package com.tianji.learning.service.impl;

import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.vo.SignResultVO;
import com.tianji.learning.mq.msg.SignInMessage;
import com.tianji.learning.service.ISignRecordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.BitFieldSubCommands;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;

/**
 * 签到记录服务实现类
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SignRecordServiceImpl implements ISignRecordService {

    private final StringRedisTemplate redisTemplate;

    private final RabbitMqHelper rabbitMqHelper;

    /**
     * @Description: 签到记录服务实现类
     * @Param: 无
     * @return
     */
    @Override
    public SignResultVO addSignRecords() {
        //1.获取用户信息
        Long userId = UserContext.getUser();
        //2.拼接key sign:uid:userId:年月   sign:uid:1:202401
        //2.1.获取日期
        LocalDate now = LocalDate.now();  //当前时间的年月
        String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM")); // :202401
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId.toString() + format;

        //3.利用bitSet命令 将签到记录保存到redis的bitmap结构中，需要校验是否已经签到
        int offset = now.getDayOfMonth() - 1; //偏移量
        Boolean setBit = redisTemplate.opsForValue().setBit(key, offset, true);
        if (Boolean.TRUE.equals(setBit)) {
            throw new BizIllegalException("今天已经签过到了");
        }

        //4.计算连续签到的天数
        int signDays  = countSignDays(key, now.getDayOfMonth());

        //6奖励积分 计算签到得分
        int rewardPoints = 0;
        switch (signDays){
            case 7:
                rewardPoints = 10;
                break;
            case 14:
                rewardPoints = 20;
                break;
            case 28:
                rewardPoints = 40;
                break;
        }

        //7.保存积分 异步通知Mq保存积分到数据库（积分明细记录）
        rabbitMqHelper.send(
                MqConstants.Exchange.LEARNING_EXCHANGE,
                MqConstants.Key.SIGN_IN,
                SignInMessage.of(userId,rewardPoints + 1) // 签到积分是基本得分+奖励积分
        );

        //8.封装返回
        SignResultVO signResultVO = new SignResultVO();
        signResultVO.setSignDays(signDays);
        signResultVO.setRewardPoints(rewardPoints);
        return signResultVO;

    }

    /**
     * @Description: 查询签到记录
     * @return
     */
    @Override
    public Byte[] querySignRecords() {
        //1.获取用户Id
        Long userId = UserContext.getUser();
        //2.拼接key
        LocalDate now = LocalDate.now();//当前时间的年月
        String format = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));//得到 :202401
        String key = RedisConstants.SIGN_RECORD_KEY_PREFIX + userId + format;

        //3.利用redis bitfield命令取本月第一天到今天所有的签到记录
        int dayOfMonth = now.getDayOfMonth();
        // bitfiled key gt u天数 0
        List<Long> bitField = redisTemplate.opsForValue().bitField(key,
                BitFieldSubCommands.create().
                        get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));
        if (CollUtils.isEmpty(bitField)){
            return new Byte[0];
        }
        Long num = bitField.get(0);
        int offset = dayOfMonth - 1;
        //4.利用与运算 和 位移运算 封装结果
        Byte[] arr = new Byte[dayOfMonth];
        while(offset >= 0){
            arr[offset] = (byte) (num & 1); //计算最后一天是否签到  赋值结果
            offset--;
            num = num >> 1;
        }
        return arr;
    }

    /**
     * @Description: 计算连续签到天数
     * @param key
     * @param len
     */
    private int countSignDays(String key, int len) {
        //1.从本月第一天开始，截止到今天，连续签到天数
        List<Long> days = redisTemplate.opsForValue().bitField(key,
                BitFieldSubCommands.create().
                        get(BitFieldSubCommands.BitFieldType.unsigned(len)).valueAt(0));
        if (CollUtils.isEmpty(days)){
            return 0;
        }
        int num = days.get(0).intValue();
        //2.定义一个计数器
        int count = 0;
        // 3.循环，与1做与运算，得到最后一个bit，判断是否为0，为0则终止，为1则继续
        while ( (num & 1) == 1){
            count++;  // 4.计数器+1
            // 5.把数字右移一位，最后一位被舍弃，倒数第二位成了最后一位
            num = num >>> 1;
        }
        return count;
    }
}
